package com.lin.utils;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTCreationException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.lin.constant.UserConstants;
import com.lin.entity.vo.UserVO;
import jakarta.servlet.http.HttpServletRequest;

import java.util.Calendar;
import java.util.Map;

public class JWTUtil {
    // 定义一个静态常量SIGNATURE，用于作为JWT令牌签名的密钥。
    // 这个密钥必须保密，并且在验证JWT时也需要使用相同的密钥。
    private static final String SIGNATURE = "$NZbC>p+>68^TbDyI,v4b:pVcWK'cf|2";

    /**
     * 生成带有指定声明(claims)和有效期的JWT令牌
     *
     * @param map 包含令牌声明信息的键值对集合，支持标准声明和自定义声明
     *            - 建议至少包含身份标识声明（如 userId）
     *            - 值类型应为简单类型（String/Number/Boolean）
     * @return 已签名的JWT令牌字符串，格式为"Header.Payload.Signature"
     * @throws IllegalArgumentException 当发生以下情况时抛出：
     *              - 密钥(SIGNATURE)不符合HMAC-SHA256要求（长度不足32字节）
     *              - 声明(claim)的键或值为null
     *              - 声明值的JSON序列化失败
     * @throws JWTCreationException 当JWT构建失败时抛出（如声明过大超过限制）
     * @implNote 关键实现细节：
     *              1. 令牌有效期固定为7分钟（通过Calendar.MINUTE设置）
     *              2. 使用HMAC-SHA256算法进行签名
     *              3. 自动添加iat（签发时间）声明
     *              4. 包含exp（过期时间）声明，值为签发时间+7分钟
     */
    public static String getToken(Map<String, String> map) {
        // 创建JWT构建器实例
        JWTCreator.Builder builder = JWT.create();

        // 设置JWT主题
        // builder.withSubject("主题名, 一般是ID或用户名");

        // 遍历map并将每个键值对作为声明添加到JWT构建器中
        map.forEach(builder::withClaim);

        // 获取当前日期时间并设置令牌过期时间为3分钟后
        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.MINUTE, 3); // 在当前日期基础上增加3分钟

        // 使用HMAC256算法和SIGNATURE密钥令对牌进行签名, 并设置过期时间
        return UserConstants.TOKEN_PREFIX + builder.withExpiresAt(instance.getTime()) // 设置过期时间
                .sign(Algorithm.HMAC256(SIGNATURE)); // 签名并返回最终的JWT令牌字符串
    }


    /**
     * 验证JWT令牌的完整性和有效性
     *
     * @param token 需要验证的JSON Web Token字符串，格式为"Header.Payload.Signature"
     *              - Header: 包含令牌类型和签名算法
     *              - Payload: 包含声明(claims)的数据体
     *              - Signature: 使用密钥生成的签名部分
     * @throws RuntimeException 当发生以下情况时抛出（包含原始异常信息）：
     *              - IllegalArgumentException: 令牌格式不合法（非三段式结构）
     *              - JWTVerificationException: 签名验证失败/令牌过期/声明校验失败
     * @implNote 异常处理包含以下具体场景：
     *              - 捕获JWTVerificationException及其子类（如TokenExpiredException）
     *              - 捕获非法参数异常（如空令牌或无效base64编码）
     *              所有异常都将包装为RuntimeException并保留原始堆栈信息
     */
    public static void verify(String token) {
        try {
            // 执行完整的JWT验证流程（包含签名校验、过期时间验证等）
            JWT.require(Algorithm.HMAC256(SIGNATURE)).build().verify(token);
        } catch (JWTVerificationException | IllegalArgumentException e) {
            // 封装原始异常信息，保持堆栈跟踪完整性
            throw new RuntimeException("JWT验证失败: " + e.getMessage(), e);
        }
    }

    /**
     * 解码并验证JWT令牌，返回包含声明(claims)的DecodedJWT对象
     *
     * @param token 需要验证的JSON Web Token字符串
     * @return DecodedJWT 包含已验证的令牌信息对象，可通过其方法获取：
     *              - Header信息：getHeader()
     *              - Payload声明：getClaim(String)
     *              - 签名信息：getSignature()
     * @throws JWTVerificationException 当发生以下情况时抛出：
     *              - 令牌过期（TokenExpiredException）
     *              - 签名无效（SignatureVerificationException）
     *              - 声明校验失败（InvalidClaimException）
     * @throws IllegalArgumentException 当令牌格式非法时抛出（如空令牌或非三段式结构）
     * @implNote 本方法相较于verify()方法的不同点：
     *              1. 返回完整的解码后对象，便于获取具体声明信息
     *              2. 不进行异常捕获，保留原始异常类型便于调用方区分错误类型
     *              3. 推荐在需要获取payload数据时使用本方法
     */
    public static DecodedJWT decodeAndVerify(String token) {
        return JWT.require(Algorithm.HMAC256(SIGNATURE)).build().verify(token);
    }
}
